// UdpReceiver.cs
//
// Author:
//    Adriano Ribeiro <adribeiro@gmail.com>
//
// Copyright (c) 2007 MonORF Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
//

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;

using Monorf;

namespace Monorf.Net
{
    
    public class UdpReceiver
    {
        private BufferManager m_receiveBuffer;
        private Dictionary<IPEndPoint, Socket> m_socketListening;
        private AsyncCallback m_receiveCompletion;
        private WaitCallback m_dataReceiveDispatch;
        private Int32 m_messageSize = 0;
        private Int64 m_bufferSize = 0;
        private IPEndPoint[] m_listenEndPoints;
        private Object m_lockObject = new Object();
            
        public UdpReceiver(WaitCallback dataReceiveDispatch,
                           IPEndPoint[] listenEndPoints,
                           Int32 messageSize,
                           Int64 bufferSize)
        {
            m_dataReceiveDispatch = dataReceiveDispatch;
            m_listenEndPoints = listenEndPoints;
            m_messageSize = messageSize;
            m_bufferSize = bufferSize;
            m_receiveBuffer = BufferManager.CreateBufferManager(bufferSize, messageSize);

            m_receiveCompletion = new AsyncCallback(ReceiveCompleteAsyncCallBack);
        }
        
        #region properties
        
        /// <summary>
        /// get local endpoint
        /// </summary>
        //      internal IPEndPoint LocalIPEndPoint{ get{ return m_localEndPoint;} }
        
        /// <summary>
        /// SyncLock object
        /// </summary>
        internal Object SyncLock{ get{return m_lockObject;} }
        
        #endregion
        
        
        #region helpers
        
        /// <summary>
        /// create socket and prepare them for start listen
        /// </summary>
        private void CreateSockets()
        {
            m_socketListening = new Dictionary<IPEndPoint, Socket>();
            
            for(Int32 i = 0; i < m_listenEndPoints.Length; i++){
                
                if(!m_socketListening.ContainsKey(m_listenEndPoints[i])){
                    
                    Socket sockListen = SocketFactory.UDPSocketBuilder();
                    sockListen.SetSocketOption(SocketOptionLevel.Socket,
                                               SocketOptionName.ReuseAddress, 1);
                    sockListen.Bind(m_listenEndPoints[i]);
                    m_socketListening.Add( m_listenEndPoints[i], sockListen);
                    
                    //star to listen
                    WaitCallback m_continousListen = new WaitCallback(ContinousListening);
                    ThreadPool.QueueUserWorkItem(m_continousListen, sockListen);
                }
            }
        }
        
        #endregion
        /// <summary>
        /// start listening
        /// </summary>
        public void StartListen()
        {
            CreateSockets();
        }
        
        /// <summary>
        /// start async listening
        /// </summary>
        /// <param name="listenSocket"></param>
        /// <returns></returns>
        private IAsyncResult BeginReceive(AsyncCallback callback, Object listenSocket)
        {

            Byte[] tempBuffer = m_receiveBuffer.TakeBuffer(m_messageSize);
            
            AsyncResult<TransmissionState> result =
                new AsyncResult<TransmissionState>(callback,
                                                   new TransmissionState((Socket)listenSocket,
                                                                         tempBuffer));
            ThreadPool.QueueUserWorkItem(ContinousListening, result);
            
            return result;
        }
        
        /// <summary>
        /// assinchonous staring listener
        /// </summary>
        /// <param name="socketState"></param>
        private void ContinousListening(Object state)
        {
            #region receive remote endpoint

            IPEndPoint client = new IPEndPoint(IPAddress.Any, 0);
            EndPoint remoteEP = (EndPoint)client;

            #endregion
            AsyncResult<TransmissionState> resultState =
                (AsyncResult<TransmissionState>)state;
            
            TransmissionState transmissionState =
                (TransmissionState)resultState.AsyncState;
            
            lock (transmissionState.SyncLock)
            {
                IAsyncResult result =
                    transmissionState.Socket.BeginReceiveFrom(transmissionState.Buffer,
                                                              0,
                                                              transmissionState.Buffer.Length,
                                                              SocketFlags.None,
                                                              ref remoteEP,
                                                              m_receiveCompletion,
                                                              state);
            }
        }
        
        /// <summary>
        /// completion estado for received data.
        /// </summary>
        /// <param name="result"></param>
        private void ReceiveCompleteAsyncCallBack(IAsyncResult result) {

            #region receive remote endpoint

            IPEndPoint client = new IPEndPoint(IPAddress.Any, 0);
            EndPoint remoteEP = (EndPoint)client;

            #endregion
            
            AsyncResult<TransmissionState> resultState =
                (AsyncResult<TransmissionState>)result.AsyncState;
            
            TransmissionState transmissionState =
                (TransmissionState)resultState.AsyncState;

            try {
                
                lock (transmissionState.SyncLock){
                    
                    Int32 bytes = 
                        transmissionState.Socket.EndReceiveFrom(result, ref remoteEP);
                    
                }
                
                resultState.Complete(transmissionState, false);
            }
            catch(SocketException ex){
                if(ex.SocketErrorCode == SocketError.SocketError)
                    return;
            }
            catch(Exception ex){
                resultState.HandleException(ex,false);
            }
            
            ContinousListening(resultState);
        }
        
        /// <summary>
        /// receive completion
        /// </summary>
        /// <param name="result"></param>
        /// <returns></returns>
        private Byte[] EndReceive(IAsyncResult result)
        {
            AsyncResult<TransmissionState> ar =
                (AsyncResult<TransmissionState>)result;

            return ar.Result.Buffer;
        }
    }
}
